package model;

import model.playingfield.PlayingField;

import org.bushe.swing.event.EventBus;

import constants.Constants;
import constants.Events;

/**
 * @author christopher
 * This is the Rules class. It contains core functionality regarding to 
 * different steps in the game - that is, how the Model should respond
 * to various events.
 * 
 * The game consists of a series of events - a player is selected, the die 
 * is rolled, a piece is selected (and moved). For each event, there is of course
 * a large number of consequences that can follow, and the role of the Rules class
 * is to provide a modular interface for defining exactly what those consequences are.
 * 
 * Whenever an event happens to Model, it will make a call to a function in Rules in
 * order to figure out how it should act. This allows for great flexibility, since the Rules 
 * class can easily be exchanged in order to facilitate new rules, as long as the interface
 * IRules is fulfilled, in which the core functions are laid out. Note that the logic will be
 * carried out in Rules.
 *
 */

public class Rules implements IRules {

	/*
	 * The following are state variables for Rules. They are critical, since their being
	 * or not being toggled determines how the game should flow. At any point during execution,
	 * whenever they have possibilities to, players are able to change these rules.
	 */

	boolean pushAllowed;
	boolean enterGoalEasyAllowed;
	
	Model model;

	public Rules(Model model) {
		this.model = model;
		this.pushAllowed = false;
		this.enterGoalEasyAllowed = false;
	}

	/**
	 * This method is called to clean up the current players turn -
	 * in other words, save all statistical details, reset all state
	 * variables, and pass the turn counter on to the next player.
	 */
	@Override
	public void cleanUp() {
		// TODO Auto-generated method stub

	}

	/**
	 * Defines how the game should deal with a dieroll. 
	 * In this instance of the rules, we will simply check if the player
	 * has any pieces to move out of his or her starting area. If not, we
	 * should pass on the turn to the next player here and now. 
	 */
	@Override
	public void dieRolled(Integer dieResult) {
		
		int player = model.getState().getCurrentPlayer();

		/*
		 * Are we at the correct location in the order of events? Check first
		 * that we have not already done any future tasks this turn, such as 
		 * moving, or rolling the die (which we should be doing now, of course)
		 */

		if(model.getState().isDieRolled() || model.getState().hasMoved()) {

			EventBus.publish(Events.ERROR.toString(), "You have already rolled the die this turn!");
			return;
		}
		model.getState().setCurrentDieValue(dieResult);
		model.getState().setDieRolled(true);
		

		/*
		 * We are in the correct order of events, but is the player even able to move
		 * any of his or her pieces with the current die value? If the value is not 1
		 * or 6, then that player needs to have at least one piece outside of the starting
		 * area in order to do anything.
		 */

		if(dieResult != 1 && dieResult != 6) {

			for(int location : model.getState().getPlayerLocations()[player]) {

				if(location <= 55) {     							 // a piece outside the starting area!
					model.getState().setCurrentDieValue(dieResult);  // remember the value of the die
					model.getState().setDieRolled(true);  			 // move to the next stage

					return;
				}
			}

			/*
			 *  Seems like all the pieces are stuck in the starting area,
			 *  so we will just have to pass the turn on to the next player,
			 *  since this one will not be able to do anything.
			 */

			EventBus.publish(Events.ERROR.toString(), "Sorry, you are not able to make any legal moves this turn!");
			model.newTurn();
			return;
		}

		/*
		 * If the player rolled a 1 or a 6, and have all pieces in the starting
		 * area, we will go on, allowing the player to select a piece to move out
		 * of the starting area.
		 */

		else {
			model.getState().setDieRolled(true);
			return;
		}
	}

	/**
	 * Alright, so the player made it past the dieroll part...time to shuffle some
	 * pieces around.
	 * 
	 * This is the single most heavy part of this setup of the rules, and involves
	 * both moving the actual pieces, as well as handling any interaction with other
	 * players at this stage.
	 * 
	 * Essentially, this stage will boil down to the following parts:
	 *    1. try moving the piece at the given location. This involves checking:
	 *      a. Is there even a piece there?
	 *      b. If there is, does that piece belong to the given player?
	 *      c. If it does (ok, ok, you MAY move it...), where is the piece?
	 *         - If it is on the starting field, it should be moved to that players entry spot.
	 * 		   - If it is on the main circuit, it should be moved forward according to the die value.
	 *           We should take special care in the course of this to make sure we enter the correct
	 *           road to the nest for that player, if that is how far we have moved.
	 * 		   - If it is on the road to the nest for that player, we should make sure that the value
	 * 	         of the die does not send the player "further away" than the nest. For example, if the
	 * 	         piece is 3 moves from the nest, then the piece should not be moved. If it is moved 
	 * 	         into the nest, it should stay there.
	 *      d. If the piece lands on another players piece, that other players piece should be moved back
	 *         to that players starting area. 
	 */
	@Override
	public void movePiece(int from, int moves) {
		
		 /*
         * Verify that the player has rolled the die before attempting to move a piece.
         */
		if(!model.getState().isDieRolled()){
			EventBus.publish(Events.ERROR.toString(), "You have to roll the die before moving stuffz");
			return;
		}
		
        /*
         * Are we at the correct location in the order of events, again? Check first of all 
         * that we have not already done a move this turn. Else, fire an ERROR.
         */
        if(model.getState().hasMoved()) {
                EventBus.publish(Events.ERROR.toString(), "You have already done a move this turn!");
                return;
        }
        
        /*
         * Is there a piece in the given location? if not, we should fire an ERROR event
         * and notify the user that that was a bad, bad, BAD move(TM).
         */
        if(!(model.getPlayingField().isSpotOccupied(from))) {
                EventBus.publish(Events.ERROR.toString(), "There is no piece in that location!");
                return;
        }

        /*
         * Create some temporary variables
         */
        PlayingField playingfield = model.getPlayingField();
		//Integer[][] pieceLocations = playingfield.getPieceLocations();
		Integer currentPlayer = model.getState().getCurrentPlayer();

		 /*
         * If there is a spot in that location, does it belong to the proper player? If not,
         * again fire an ERROR and break.
         */ 
        if(!playingfield.whoOccupiesSpot(from).equals(currentPlayer)) {
                EventBus.publish(Events.ERROR.toString(), "You are not allowed to touch that piece! Shoo!");
                return;
        }

        /*
         * Case 1: The piece is located in a players starting area. In this case, just move it to that
         * players entryspot for the circuit. 
         */
        if( 56 <= from && from <= 71 ) {
        	
        	/*
        	 * Fire an error if the player dont have a legitimate way of moving his or her
        	 * piece from the nest. This means:
        	 *   - if the dievalue is not 1 or 6, an error should be raised
        	 *   - if there already is a piece belonging to that player in the
        	 *     entry spot, an error should also be raised.
        	 */
        	if(1 != moves && moves != 6 ) {
        		EventBus.publish(Events.ERROR.toString(), "You cannot move a piece from the starting location, unless you roll a 1 or a 6");
        		return;
        	}
        	
        	controlledMove(from, moves);
        	hasWonGame();
        	model.getAudio().playSound(Constants.Sounds.moveOut);
        	model.newTurn();
        	return;
        }
        
        /*
         * Case 2: the Piece is located on the main circuit. If this is the case, then we will just
         * go ahead and move them forward for the set number of steps.
         */
        if( 0 <= from && from <= 39){
        	controlledMove(from, moves);
        	model.getAudio().playSound(Constants.Sounds.moveStep);
        	hasWonGame();
        	model.newTurn();
        	return;
        }
        
        /*
         *  Case 3: the Piece is located on on of the runways leading up to the nest.
         */
        if( 40 < from && from <= 71 ) {
        	
        	
        }
	}

	@Override
	public void playerSelected(Player player) {
		// TODO Auto-generated method stub
	}

	@Override
	public void setPushAllowedOn() {
		this.pushAllowed = true;
	}

	@Override
	public void setPushAllowedOff() {
		this.pushAllowed = false;
	}

	@Override
	public boolean isPushAllowedOn() {
		return this.pushAllowed;
	}

	@Override
	public void setEnterGoalEasyOn() {
		this.enterGoalEasyAllowed = true;
	}

	@Override
	public void setEnterGoalEasyOff() {
		this.enterGoalEasyAllowed = false;
	}

	@Override
	public boolean isEnterGoalEasyOn() {
		return this.enterGoalEasyAllowed;
	}

	/**
	 * This method handles the movement of pieces on the field. 
	 * 
	 * If a player lands on a square where his/her own piece already is, nothing 
	 * should happen apart from an error being displayed.
	 * 
	 * If a player lands on another players piece, and the push option is enabled,
	 * then that piece should be returned to its starting area, and the new players
	 * piece should replace it on the circuit.
	 * @param location
	 */
	private void controlledMove(int from, int moves) {
		System.out.println("Ctrl Move");
		
		int currentPlayer = model.getState().getCurrentPlayer();
		PlayingField playingfield = model.getPlayingField();
		int destinationSpot = playingfield.findDestinationSpot(from, moves);
		
		/*
		 * If the destination spot is not occupied, just move there and go along.
		 */
    	if(!playingfield.isSpotOccupied(destinationSpot)) {
    		playingfield.movePiece(from, moves);
    		
    		//Update the score for the player (score == number of moves)
    		int old = model.getState().getPlayer(currentPlayer).getScore();
    		model.getState().getPlayer(currentPlayer).setScore(old+moves);
    		
    		return;
    	}
    	
    	/*
    	 * If the destination spot is in the nest (or beyond it, depending on the rules,
    	 * remove the piece from play.
    	 */
    	if(destinationSpot == -1) {
    		playingfield.removePiece(from);
    		return;
    	}
    	
    	/*
    	 * If the destination spot is already occupied by a Piece belonging to the
    	 * current Player, invalidate the move and move on.
    	 */
    	if( playingfield.whoOccupiesSpot(destinationSpot) == currentPlayer ) {
    		EventBus.publish(Events.ERROR.toString(), "That spot is already occupied by your own piece!");
			return;
    	}
    	
    	/*
    	 * If the destination Spot is occupied by a Piece belonging to another Player, fight them
    	 * to the death! 
    	 */
    	if( playingfield.whoOccupiesSpot(destinationSpot) != currentPlayer ) {
    		
    		Battle b = new Battle();
    		model.getAudio().playSound(Constants.Sounds.battle);
    		boolean win = b.askPlayer();
    		if(win){
    			model.getAudio().playSound(Constants.Sounds.battleWin);
    			playingfield.moveToStart(destinationSpot);
    			playingfield.movePiece(from, moves);
    		}
    		else{
    			model.getAudio().playSound(Constants.Sounds.battleLose);
    			playingfield.moveToStart(from);
    		}
    		return;
    	}
    	
    	if(destinationSpot >=40)
    		model.getAudio().playSound(Constants.Sounds.moveHome);
	}
	
	/**
	 * This method checks whether a given player has won the game. If this is the case,
	 * then, depending on the rules, that player should be removed from the game, and 
	 * the game can either continue, exit, or whatever is defined by the current set 
	 * of rules.
	 */
	public void hasWonGame() {
		
		boolean won = false;
		
		int playerID = model.getState().getCurrentPlayer();
		Player player = model.getState().getPlayer(playerID);
		
		for(int piece : model.getState().getPlayerLocations()[playerID]) {
			
			if(playerID == 0){
				if(piece == 41 || piece == 42 || piece == 43 || piece == 40 ){
					won = true;
				}
				else{
					return;
				}
			}
			if(playerID == 1){
				if(piece == 44 || piece == 45 || piece == 46 || piece == 47 ){
					won = true;
				}
				else{
					return;
				}
			}
			if(playerID == 2){
				if(piece == 48 || piece == 49 || piece == 50 || piece == 51 ){
					won = true;
				}
				else{
					return;
				}
			}
			if(playerID == 3){
				if(piece == 52 || piece == 53 || piece == 54 || piece == 55 ){
					won = true;
				}
				else{
					return;
				}
			}
		}
		
		if(won) {
			System.out.println("Player won!");
			//Play a the winner sound "Sound of music!!" 
			model.getAudio().playSound(Constants.Sounds.winGame);
			
			HighScore high = new HighScore();
			high.addNewPlayer(player, player.getScore());
			
			//Update the view 
			EventBus.publish(Events.WIN.toString(),player.getPlayerName());
		}
		else
			return;
	}
}